/* *
 * --ライセンスについて--
 *
 * 「本ファイルの内容は Mozilla Public License Version 1.1 (「本ライセンス」)
 * の適用を受けます。
 * 本ライセンスに従わない限り本ファイルを使用することはできません。
 * 本ライセンスのコピーは http://www.mozilla.org/MPL/ から入手できます。
 *
 * 本ライセンスに基づき配布されるソフトウェアは、「現状のまま」で配布されるものであり、
 * 明示的か黙示的かを問わず、いかなる種類の保証も行われません。
 * 本ライセンス上の権利および制限を定める具体的な文言は、本ライセンスを参照してください。
 *
 * オリジナルコードおよび初期開発者は、N_H (h.10x64@gmail.com) です。
 *
 * N_H によって作成された部分の著作権表示は次のとおりです。
 *
 * Copyright (C) 2011 - 2012
 *
 * このファイルの内容は、上記に代えて、
 * GNU General License version2 以降 (以下 GPL とする)、
 * GNU Lesser General Public License Version 2.1 以降 (以下 LGPL とする)、
 * の条件に従って使用することも可能です。
 * この場合、このファイルの使用には上記の条項ではなく GPL または LGPL の条項が適用されます。
 * このファイルの他者による使用を GPL または LGPL の条件によってのみ許可し、
 * MPL による使用を許可したくない対象者は、上記の条項を削除することでその意思を示し、
 * 上記条項を GPL または LGPL で義務付けられている告知およびその他の条項に置き換えてください。
 * 対象者が上記の条項を削除しない場合、
 * 受領者は MPL または GPL または LGPL ライセンスのいずれによってもこのファイルを
 * 使用することができます。」
 *
 * -- License --
 *
 * Version: MPL 1.1/GPL 2.0/LGPL 2.1
 *
 * The contents of this file are subject to the Mozilla Public License Version
 * 1.1 (the "License"); you may not use this file except in compliance with
 * the License。You may obtain a copy of the License at
 * http://www.mozilla.org/MPL/
 *
 * Software distributed under the License is distributed on an "AS IS" basis,
 * WITHOUT WARRANTY OF ANY KIND、either express or implied。See the License
 * for the specific language governing rights and limitations under the
 * License.
 *
 * The Initial Developer of the Original Code is
 *   N_H (h.10x64@gmail.com).
 *
 * Portions created by the Initial Developer are Copyright (C) 2011 - 2012
 * the Initial Developer。All Rights Reserved.
 *
 * Alternatively、the contents of this file may be used under the terms of
 * either the GNU General Public License Version 2 or later (the "GPL")、or
 * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
 * in which case the provisions of the GPL or the LGPL are applicable instead
 * of those above。If you wish to allow use of your version of this file only
 * under the terms of either the GPL or the LGPL、and not to allow others to
 * use your version of this file under the terms of the MPL、indicate your
 * decision by deleting the provisions above and replace them with the notice
 * and other provisions required by the GPL or the LGPL。If you do not delete
 * the provisions above、a recipient may use your version of this file under
 * the terms of any one of the MPL、the GPL or the LGPL.
 *
 * */
package com.magiciansforest.audio.vst.binaural;

import java.awt.BasicStroke;
import java.awt.Color;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Point;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.awt.event.MouseMotionListener;
import javax.swing.JComponent;
import javax.swing.event.ChangeEvent;
import javax.swing.event.ChangeListener;

/**
 *
 * @author N_H <h.10x64@gmail.com>
 */
public class Grid extends JComponent
        implements ChangeListener, MouseListener, MouseMotionListener {

    public static final int YZ = 0;
    public static final int XZ = 1;
    private static final int SND_W = 6;
    private static final int SND_H = SND_W;
    private static final int BOX_WIDTH = 2;
    private static final byte ACCER = 0x01;
    private static final byte BREAK = 0x02;
    private static final byte LEFT = 0x04;
    private static final byte RIGHT = 0x08;
    private static final double ACCERALATION = 0.5;
    private static final double ANGULAR_VELOCITY = Math.PI / 18;
    private int mode;
    private int tick;
    private boolean drag;
    private byte moveState;
    private boolean isSelected = false;
    private SoundEnvironment env;

    public Grid(SoundEnvironment env, int mode, int tick) {
        this.env = env;
        this.mode = mode;
        this.tick = tick;

        env.addChangeListener(this);
        addMouseListener(this);
        addMouseMotionListener(this);
    }

    @Override
    public void mouseClicked(MouseEvent e) {
        if (isSelected) {
            setSourcePosition(e.getX(), e.getY());
        }
    }

    @Override
    public void mouseEntered(MouseEvent e) {
        isSelected = true;
        repaint();
    }

    @Override
    public void mouseExited(MouseEvent e) {
        isSelected = false;
        repaint();
    }

    @Override
    public void mousePressed(MouseEvent e) {
        drag = true;
    }

    @Override
    public void mouseReleased(MouseEvent e) {
        drag = false;
    }

    @Override
    public void mouseDragged(MouseEvent e) {
        if (drag && isSelected) {
            setSourcePosition(e.getX(), e.getY());
        }
    }

    @Override
    public void mouseMoved(MouseEvent e) {
        if (drag && isSelected) {
            setSourcePosition(e.getX(), e.getY());
        }
    }

    @Override
    public void stateChanged(ChangeEvent e) {
        if (e.getSource().equals(env)) {
            repaint();
        }
    }

    @Override
    public void paint(Graphics g) {
        Graphics2D g2d = (Graphics2D) g;
        int w = getWidth();
        int h = getHeight();
        int cx = w / 2;
        int cy = h / 2;

        if (isSelected) {
            g2d.setColor(new Color(0.25f, 0, 0));
            g2d.setStroke(new BasicStroke(BOX_WIDTH));
            g2d.drawRect(BOX_WIDTH, BOX_WIDTH, getWidth() - 2 * BOX_WIDTH, getHeight() - 2 * BOX_WIDTH);
            g2d.setColor(new Color(0.2f, 0, 0));
            g2d.drawRect(2 * BOX_WIDTH, 2 * BOX_WIDTH, getWidth() - 4 * BOX_WIDTH, getHeight() - 4 * BOX_WIDTH);
            g2d.setStroke(new BasicStroke(1));
        }

        g2d.setColor(Color.darkGray);
        for (int i = 0; i <= w; i += tick) {
            g2d.drawLine(i, 0, i, h);
        }
        for (int i = 0; i <= h; i += tick) {
            g2d.drawLine(0, i, w, i);
        }

        Vector3 obsPos = env.getObserverPosition();
        Spherical obsDir = env.getObserverDirection();
        Color obsPosColor = new Color(0.75f, 0.0f, 0.0f);
        Color obsDirColor = new Color(1.0f, 1.0f, 1.0f);
        Vector3 srcPos = env.getSourcePosition();
        Spherical srcDir = env.getSourceDirection();
        Color srcPosColor = new Color(0.25f, 0.25f, 1.0f);
        Color srcDirColor = new Color(1.0f, 1.0f, 1.0f);

        g2d.setColor(Color.RED);
        double px, py, pz;
        switch (mode) {
            case YZ:
                px = srcPos.x * getHeight() / env.getWidth();
                for (int dz = -100; dz < 100; dz += 10) {
                    pz = obsPos.z * getWidth() / env.getDepth() + dz;
                    Point p0 = new Point(dz, (int) Math.round(-Math.sqrt(pz * pz + px * px)));
                    Point p1 = new Point(dz + 10, (int) Math.round(-Math.sqrt((pz + 10) * (pz + 10) + px * px)));
                    if (Math.abs(p0.y) < 100 && Math.abs(p1.y) < 100) {
                        g2d.drawLine(cx + p0.x, cy - p0.y, cx + p1.x, cy - p1.y);
                    }
                }
                break;
            default: //XZ
                if (srcPos.y < 0) {
                    py = srcPos.y * getWidth() / env.getHeight();
                    px = obsPos.x * getWidth() / env.getWidth();
                    pz = obsPos.z * getHeight() / env.getDepth();
                    int r = (int) Math.round(Math.abs(py));
                    g2d.drawOval((int) (cx + px - r), (int) (cy - pz - r), r * 2, r * 2);
                }
                break;
        }

        switch (mode) {
            case YZ:
                if (srcPos.x > obsPos.x) {
                    drawSoundObject(g2d, obsPosColor, obsDirColor, cx, cy, obsPos, obsDir, mode);
                    drawSoundObject(g2d, srcPosColor, srcDirColor, cx, cy, srcPos, null, mode);
                } else {
                    drawSoundObject(g2d, srcPosColor, srcDirColor, cx, cy, srcPos, null, mode);
                    drawSoundObject(g2d, obsPosColor, obsDirColor, cx, cy, obsPos, obsDir, mode);
                }
                break;
            default: //XZ
                if (srcPos.y > obsPos.y) {
                    drawSoundObject(g2d, obsPosColor, obsDirColor, cx, cy, obsPos, obsDir, mode);
                    drawSoundObject(g2d, srcPosColor, srcDirColor, cx, cy, srcPos, null, mode);
                } else {
                    drawSoundObject(g2d, srcPosColor, srcDirColor, cx, cy, srcPos, null, mode);
                    drawSoundObject(g2d, obsPosColor, obsDirColor, cx, cy, obsPos, obsDir, mode);
                }
                break;
        }
    }

    private void setSourcePosition(int mouseX, int mouseY) {
        int x = mouseX - getWidth() / 2;
        int y = mouseY - getHeight() / 2;

        Vector3 srcPos = new Vector3(env.getSourcePosition());
        switch (mode) {
            case YZ:
                srcPos.y = -y * (env.getHeight() / getHeight());
                srcPos.z = -x * (env.getDepth() / getWidth());
                break;
            default: //XZ
                srcPos.x = x * (env.getDepth() / getWidth());
                srcPos.z = -y * (env.getWidth() / getHeight());
                break;
        }
        env.setSourcePosition(srcPos);
        
        env.update(0);
    }

    private void drawSoundObject(Graphics2D g2d, Color pColor, Color dColor, int cx, int cy, Vector3 pos, Spherical dir, int mode) {
        int drawPosX, drawPosY;

        switch (mode) {
            case YZ:
                drawPosX = (int) Math.round(-pos.z * getWidth() / env.getDepth());
                drawPosY = (int) Math.round(-pos.y * getHeight() / env.getHeight());
                break;
            default: //XZ
                drawPosX = (int) Math.round(pos.x * getWidth() / env.getDepth());
                drawPosY = (int) Math.round(-pos.z * getHeight() / env.getWidth());
                break;
        }

        int sndX = cx + drawPosX;
        int sndY = cy + drawPosY;

        float ratio;
        switch (mode) {
            case YZ:
                ratio = (float) (0.25 * pos.x / env.getWidth() + 0.75);
                break;
            default:
                ratio = (float) (0.25 * pos.y / env.getHeight() + 0.75);
                break;
        }
        float r = (float) Math.min(1.0, Math.max(-1.0, (double) pColor.getRed() / 256 * ratio));
        float g = (float) Math.min(1.0, Math.max(-1.0, (double) pColor.getGreen() / 256 * ratio));
        float b = (float) Math.min(1.0, Math.max(-1.0, (double) pColor.getBlue() / 256 * ratio));
        g2d.setColor(new Color(r, g, b));

        g2d.fillOval(sndX - SND_W / 2, sndY - SND_H / 2, SND_W, SND_H);

        g2d.setColor(dColor);
        if (dir != null) {
            dir.dist = 5.0;
            Vector3 vDir = dir.toVector3();
            switch (mode) {
                case YZ:
                    g2d.drawLine(sndX, sndY, (int) (sndX - vDir.z), (int) (sndY - vDir.y));
                    break;
                default: //XZ
                    g2d.drawLine(sndX, sndY, (int) (sndX + vDir.x), (int) (sndY - vDir.z));
                    break;
            }
        }
    }
}
